package com.bestv.search.engine.command;

import com.bestv.search.common.cache.SystemCache;
import com.bestv.search.common.dataSource.DataSourceKey;
import com.bestv.search.common.dataSource.DataSourceKeyUtil;
import com.bestv.search.common.model.TopKeyword;
import com.bestv.search.common.service.TopKeywordManager;
import com.bestv.search.engine.analyzer.SuggestionAnalyzer;
import com.bestv.search.engine.compass.extend.RAMDirectoryManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.RAMDirectory;
import org.dom4j.Document;
import org.dom4j.Node;

import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Michael Wang
 */

// $Id: SuggestCommand.java 97 2012-05-22 06:48:50Z wbwang $
public class SuggestCommand {

    private static final int DEFAULT_SUGGESTION_LIMIT = 10;
    private static boolean isRunning = false;
    private static ReentrantLock lock = new ReentrantLock();
    protected final Log log = LogFactory.getLog(getClass());
    private RAMDirectory ramDir = RAMDirectoryManager.getInstance();
    private TopKeywordManager topKeywordManager;
    private SystemCache systemCache;

    public String suggest(Document doc) {
        Node keyNode = doc.selectSingleNode("/request/keyword");
        if (keyNode == null || "".equals(keyNode.getText().trim())) {
            log.error("Suggest request must have a keyword element.");
            log.error(doc.asXML());
            return xmlPiece("1", "请求消息格式错误");
        }
        Node userNode = doc.selectSingleNode("/request/userId");
        if (userNode == null || "".equals(userNode.getText().trim())) {
            log.error("Suggest request must have a userId element.");
            log.error(doc.asXML());
            return xmlPiece("1", "请求消息格式错误");
        }
        Node limitNode = doc.selectSingleNode("/request/limit");
        int limit = DEFAULT_SUGGESTION_LIMIT;
        if (limitNode != null && !"".equals(limitNode.getText().trim())) {
            try {
                limit = Integer.valueOf(limitNode.getText().trim());
            } catch (Exception e) {
                log.warn("Parse limit node to integer error!");
                limit = DEFAULT_SUGGESTION_LIMIT;
            }
        }
        return suggest(keyNode.getText().trim(), limit);
    }

    private String suggest(String keyword, int limit) {
        try {
            log.debug("SuggestCommand obtained the RAMDirectory object " + RAMDirectoryManager.getInstance());
            RAMDirectory suggestRAM = RAMDirectoryManager.getInstance();
            String files[] = suggestRAM.list();
            if (files == null || files.length == 0) {
                boolean result = createOrUpdateIndex();
                if (!result) {
                    return xmlPiece("2", "内部错误");
                }
            }

            IndexSearcher is = new IndexSearcher(RAMDirectoryManager.getInstance());
            MultiFieldQueryParser pq = new MultiFieldQueryParser(new String[]{"keyword", "py", "num"},
                    new SuggestionAnalyzer(), null);
            Sort sort = new Sort(new SortField("count", SortField.INT, true));
            TopDocs topDocs = is.search(pq.parse(keyword + "*"), null, limit, sort);
            StringBuffer result = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?><result>");
            int n = limit < topDocs.totalHits ? limit : topDocs.totalHits;
            for (int i = 0; i < n; i++) {
                org.apache.lucene.document.Document doc = is.doc(topDocs.scoreDocs[i].doc);
                result.append("<record><suggest>").append(doc.get("keyword")).append("</suggest><index>").append(i)
                        .append("</index></record>");
            }
            result.append("</result>");
            return result.toString();
        } catch (Exception e) {
            log.error("Error occured while searching index in memory.");
            e.printStackTrace();
            return xmlPiece("2", "内部错误");
        }
    }

    private String xmlPiece(String code, String message) {
        return "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><error><code>" + code + "</code><message>" + message
                + "</message></error>";
    }

    public boolean createOrUpdateIndex() {
        if (!isRunning) {
            try {
                isRunning = true;
                lock.lock();
                log.info("Start Suggestion index init @" + new Date());
                DataSourceKey key = DataSourceKeyUtil.convert(systemCache.getLocalDataSourceKey());
                List<TopKeyword> topList = topKeywordManager.getAllTopKeywords(key);
                for (TopKeyword topKeyword : topList) {
                    String filteredWord = filterSpecialWord(topKeyword.getKeyword());
                    int docId = indexExistsInMemory(filteredWord, new SuggestionAnalyzer());
                    topKeyword.setKeyword(filteredWord);
                    if (docId != -1) {
                        updateDocInIndex(docId, topKeyword);
                    } else {
                        createDocForIndex(topKeyword);
                    }
                }
            } catch (Exception e) {
                log.error("Suggestion index failed @" + new Date());
                e.printStackTrace();
                return false;
            } finally {
                isRunning = false;
                lock.unlock();
                log.info("End Suggestion index init @" + new Date());
            }
            return true;
        } else {
            log.error("Suggestion index is creating @" + new Date());
            return false;
        }
    }

    private String filterSpecialWord(String keyword) {
        try {
            Matcher matcher = Pattern.compile("([\\w]+|[ⅠⅡⅢIV（）()\\[\\]，,-\\.·]+)").matcher(keyword);
            if (matcher.find()) {
                String sWord = matcher.group();
                String filteredWord = keyword.substring(0, keyword.indexOf(sWord));
                return "".equals(filteredWord) ? keyword : filteredWord;
            } else {
                return keyword;
            }
        } catch (Exception e) {
            return keyword;
        }
    }

    private void updateDocInIndex(int docId, TopKeyword topKeyword) throws IOException {
        IndexSearcher is = new IndexSearcher(ramDir);
        org.apache.lucene.document.Document doc = is.doc(docId);

        //过滤count为0的keyword
        if(topKeyword.getCount()==0) {
            is.getIndexReader().deleteDocument(docId);
            is.close();
        }else{
            Field countField = doc.getField("count");
            int countInDoc = Integer.valueOf(countField.stringValue());
            doc.removeField("count");
            int count = topKeyword.getCount() + countInDoc;
            doc.add(new Field("count", String.valueOf(count), Field.Store.YES, Field.Index.NOT_ANALYZED));
            if (IndexWriter.isLocked(ramDir)) {
                IndexWriter.unlock(ramDir);
            }
            is.getIndexReader().deleteDocument(docId);
            is.close();
            @SuppressWarnings("deprecation")
            IndexWriter writer = new IndexWriter(ramDir, new SuggestionAnalyzer());
            writer.addDocument(doc);
            writer.close();
        }
    }

    private void createDocForIndex(TopKeyword topKeyword) throws IOException {
        if(topKeyword.getCount()>0){
            org.apache.lucene.document.Document doc = new org.apache.lucene.document.Document();
            doc.add(new Field("keyword", topKeyword.getKeyword().trim(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.add(new Field("py", topKeyword.getPy().trim(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.add(new Field("num", topKeyword.getNum().trim(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.add(new Field("count", String.valueOf(topKeyword.getCount()), Field.Store.YES, Field.Index.NOT_ANALYZED));
            @SuppressWarnings("deprecation")
            IndexWriter writer = new IndexWriter(ramDir, new SuggestionAnalyzer());
            writer.addDocument(doc);
            writer.close();
        }
    }

    private int indexExistsInMemory(String keyword, Analyzer analyzer) {
        try {
            if ("".equals(keyword))
                return -1;
            else
                keyword = keyword.trim();
            IndexSearcher is = new IndexSearcher(ramDir);
            QueryParser pq = new QueryParser("keyword", analyzer);
            TopDocs topDocs = is.search(pq.parse(keyword + "*"), 1);
            return topDocs.totalHits > 0 ? topDocs.scoreDocs[0].doc : -1;
        } catch (Exception e) {
            return -1;
        }
    }

    public TopKeywordManager getTopKeywordManager() {
        return topKeywordManager;
    }

    public void setTopKeywordManager(TopKeywordManager topKeywordManager) {
        this.topKeywordManager = topKeywordManager;
    }

    public SystemCache getSystemCache() {
        return systemCache;
    }

    public void setSystemCache(SystemCache systemCache) {
        this.systemCache = systemCache;
    }
}
